package com.cosmoplat.mom.iot.client;

import com.cosmoplat.mom.iot.client.api.param.*;
import com.cosmoplat.mom.iot.client.api.response.DeviceAlarmRecord;
import com.cosmoplat.mom.iot.client.api.response.DeviceDataItem;
import com.cosmoplat.mom.iot.client.api.response.DeviceHistoryData;
import com.cosmoplat.mom.iot.client.api.response.DeviceInfo;
import com.cosmoplat.mom.iot.client.api.response.DeviceTypeInfo;
import com.cosmoplat.mom.iot.client.api.response.IotResponse;
import com.cosmoplat.mom.iot.client.api.response.PageResponse;
import com.cosmoplat.mom.iot.client.config.IotClientProperties;
import com.cosmoplat.mom.iot.client.constants.IotApis;
import com.cosmoplat.mom.iot.client.exception.IotClientException;
import com.cosmoplat.mom.iot.client.http.HttpRequest;
import com.cosmoplat.mom.iot.client.utils.ParamUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author yotta
 * @date 2025/6/16
 */
@Slf4j
public class RestTemplateIotClient implements IotClient {
    @Setter
    private RestTemplate restTemplate;

    private final IotClientProperties iotClientProperties;

    private final LoadingCache<String, String> tokenCache = CacheBuilder
            .newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    IotResponse<String> auth = auth();
                    return auth.getResultIfSuccess();
                }
            });

    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public RestTemplateIotClient(IotClientProperties iotClientProperties) {
        this.iotClientProperties = iotClientProperties;
        this.restTemplate = new RestTemplate();
        //this.auth();
    }

    @Override
    public IotResponse<String> auth() {
        AuthParam authParam = new AuthParam();
        authParam.setAccount(iotClientProperties.getUsername());
        authParam.setPassword(iotClientProperties.getPassword());
        authParam.setTenantCode(iotClientProperties.getTenantCode());
        authParam.setType(iotClientProperties.getType());
        authParam.setSource(iotClientProperties.getSource());
        return auth(authParam);
    }


    @Override
    public IotResponse<List<DeviceTypeInfo>> getDeviceTypeList(GetDeviceTypeListParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<List<DeviceTypeInfo>>>() {
        });
    }
    
    @Override
    public IotResponse<List<DeviceInfo>> getDeviceListByTypeId(GetDeviceListByTypeIdParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<List<DeviceInfo>>>() {
        });
    }
    
    @Override
    public IotResponse<List<DeviceDataItem>> getDataItemListByDeviceId(GetDataItemListByDeviceIdParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<List<DeviceDataItem>>>() {
        });
    }
    
    @Override
    public IotResponse<PageResponse<DeviceAlarmRecord>> getDeviceAlarmRecordPage(GetDeviceAlarmRecordPageParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<PageResponse<DeviceAlarmRecord>>>() {
        });
    }
    
    @Override
    public IotResponse<PageResponse<DeviceHistoryData>> getDeviceHistoryDataPage(GetDeviceHistoryDataPageParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<PageResponse<DeviceHistoryData>>>() {
        });
    }

    @Override
    public IotResponse<List<DeviceDataItem>> getDataItemListByDeviceCode(GetDataItemListByDeviceCodeParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        return parseResponse(responseEntity, new TypeReference<IotResponse<List<DeviceDataItem>>>() {
        });
    }


    private IotResponse<String> auth(AuthParam param) {
        ResponseEntity<String> responseEntity = execute(param);
        IotResponse<String> response = parseResponse(responseEntity, new TypeReference<IotResponse<String>>() {
        });
        String token = response.getResultIfSuccess();
        tokenCache.put(iotClientProperties.getUsername(), token);
        return response;
    }

    protected ResponseEntity<String> execute(BaseIotParam param) {
        param.check();
        HttpRequest httpRequest = buildRequest(param);
        return doExecute(httpRequest);
    }

    protected ResponseEntity<String> doExecute(HttpRequest request) {
        try {
            HttpMethod httpMethod = resolveHttpMethod(request);
            String completeUrl = request.completeUrl();
            HttpHeaders headers = convertHeaders(request);
            HttpEntity<?> httpEntity = buildHttpEntity(request, headers);
            ResponseEntity<String> exchange = restTemplate.exchange(
                    completeUrl,
                    httpMethod,
                    httpEntity,
                    String.class);
            log.info("请求URL: [{}]:{}; body: {}, 响应结果为 {}", httpMethod.name(),completeUrl,request.getBody(), exchange.getBody());
            return exchange;
        } catch (Exception e) {
            log.info("请求URL: {}; 参数为 {}", request.completeUrl(), request, e);
            throw e;
        }
    }

    private HttpRequest buildRequest(BaseIotParam param) {
        IotApi api = param.api();
        String url = iotClientProperties.getServerUrl() + api.getPath();
        HttpRequest request = HttpRequest.of(api.getMethod(), url)
                .contentType(api.getContentType());
        if (api != IotApis.AUTH) {
            request.addHeader(TOKEN_NAME, getToken(iotClientProperties.getUsername()));
        }
        request = param.customizeHttpRequest(request);
        return request;
    }


    private String getToken(String accountId) {
        return tokenCache.getUnchecked(accountId);
    }



    private HttpHeaders convertHeaders(HttpRequest request) {
        HttpHeaders headers = new HttpHeaders();
        for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
            headers.add(entry.getKey(), entry.getValue());
        }
        return headers;
    }

    private HttpMethod resolveHttpMethod(HttpRequest request) {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        ParamUtil.nonNull(httpMethod, "HttpMethod is null");
        return httpMethod;
    }

    private HttpEntity<?> buildHttpEntity(HttpRequest request, HttpHeaders headers) {
        HttpEntity<?> httpEntity;
        if (request.getBody() != null) {
            httpEntity = new HttpEntity<>(request.getBody(), headers);
        } else {
            httpEntity = new HttpEntity<>(headers);
        }
        return httpEntity;
    }

    private <T> IotResponse<T> parseResponse(ResponseEntity<String> responseEntity, TypeReference<IotResponse<T>> valueTypeRef) {
        try {
            return MAPPER.readValue(responseEntity.getBody(), valueTypeRef);
        } catch (JsonProcessingException e) {
            log.error("parseResponse error, response is {}", responseEntity.getBody(), e);
            throw new IotClientException(e);
        }
    }
}
